--  ---------------------------------------------------------------------------
--  @File: command_line.adb
--
--  @Project: LiberAda
--  @Version: 1.0
--  @Created: 2009-09-18
--  @Author:  Markus Knau�, Felix Krause, Julian Trischler
--
--  @Description:
--    For a detailed description of this package (@see command_line.ads).
--  @Implementation:
--    The argument keys are stored in a map as keys. Values for the keys
--    are vectors. Each value is stored as unbounded string.
--
--  @Modifications:
--    +2009-09-18 MK: Initial release
--    +2011-04-06 FK: Removed unneccessary with clause;
--                    Declared some vars constant - stops compiler warnings
--    +2011-07-14 JT: Corrected documentation
--  ---------------------------------------------------------------------------
with Ada.Command_Line;
with Ada.Containers;
with Ada.Containers.Hashed_Maps;
with Ada.Containers.Vectors;
with Ada.Strings.Fixed;

package body Command_Line is

   package ASU renames Ada.Strings.Unbounded;

   --  ------------------------------------------------------------------------
   --  @Package: Values_Vector
   --  @Purpose:
   --    This vector is used to store the values with their argument key.
   package Values_Vector is new Ada.Containers.Vectors
     (Index_Type   => Natural,
      Element_Type => ASU.Unbounded_String,
      "="          => ASU."=");

   --  ------------------------------------------------------------------------
   --  @Procedure: Parse_Command_Line
   procedure Parse_Command_Line;

   --  ------------------------------------------------------------------------
   --  @Function: Is_Key
   function Is_Key (Arg : in String) return Boolean;

   --  ------------------------------------------------------------------------
   --  @Function: Key_Hash
   function Key_Hash
     (Key : in ASU.Unbounded_String) return Ada.Containers.Hash_Type;

   --  ------------------------------------------------------------------------
   --  @Package: Arguments_Map
   --  @Purpose:
   --    This map is used to store the keys of the arguments given.
   package Arguments_Map is new Ada.Containers.Hashed_Maps
     (Key_Type        => ASU.Unbounded_String,
      Element_Type    => Values_Vector.Vector,
      Hash            => Key_Hash,
      Equivalent_Keys => ASU."=",
      "=" => Values_Vector."=");

   --  ------------------------------------------------------------------------
   --  @Global_Variable: Arguments
   --  @Purpose:
   --    Stores the arguments given on the command line with their associated
   --    values.
   Arguments : Arguments_Map.Map;

   --  ------------------------------------------------------------------------
   --  @Global_Variable: Arg_Ind
   --  @Purpose:
   --    The character that distinguishes keys from values in the command line
   --    arguments. For instance, '-i' is an key and 'x' is an value.
   Key_Ind : String := "-";

   --  ------------------------------------------------------------------------
   --   @Function: Key_Hash
   --   @Description:
   --     This function calculates the
   --     hash for a key. The hash is used as index for storing and
   --     accessing the key in the map.
   --   @Implementation:
   --     The hash is calculated using the Divisions-Rest-Methode.
   --     (@see http://de.wikipedia.org/wiki/Divisions-Rest-Methode)
   function Key_Hash
     (Key : in ASU.Unbounded_String) return Ada.Containers.Hash_Type is
      Hash_Value : Natural           := 0;
      Key_Str    : constant String   := ASU.To_String (Key);
      M          : constant          := 2**31 - 1;
   begin
      for Char_Itr in Key_Str'Range loop
         if (Char_Itr > Key_Str'First) then
            Hash_Value := (Hash_Value * Character'Size + Character'Pos (
              Key_Str (Char_Itr))) mod M;
         else
            Hash_Value := Character'Pos (Key_Str (Char_Itr)) mod M;
         end if;
      end loop;
      return Ada.Containers.Hash_Type (Hash_Value);
   end Key_Hash;

   --  ------------------------------------------------------------------------
   --  @Function: Is_Argument
   --  @Description:
   --    Determines if the given argument is a key.
   function Is_Key (Arg : in String) return Boolean is
   begin
      return Ada.Strings.Fixed.Head (Arg, Key_Ind'Length) = Key_Ind;
   end Is_Key;

   --  ------------------------------------------------------------------------
   --  @Procedure: Parse_Command_Line
   --  @Description:
   --    Parses the arguments given on the command line.
   --  @Implementation:
   --    Each argument is checked if it is a key. The check is done by looking
   --    for the (@see Key_Ind) string at the beginning of the argument. If
   --    the argument it is tried to parse the following arguments as values.
   procedure Parse_Command_Line is

      --  ---------------------------------------------------------------------
      --  @Procedure: Parse_Value
      procedure Parse_Values (Key : in String);

      --  ---------------------------------------------------------------------
      --  @Variable: Idx
      --  @Purpose:
      --    The index of the actually processed command line argument.
      --    (@see Parse_Values) and (@see Parse_Command_Line) share this index.
      Idx : Natural := 1;

      --  ---------------------------------------------------------------------
      --  @Procedure: Parse_Values
      --  @Description:
      --    Tries to parse the values that are given after a key. The values
      --     are stored with the key.
      procedure Parse_Values (Key : in String) is
         Values : Values_Vector.Vector;
      begin
         Idx := Idx + 1;
         while Idx <= Ada.Command_Line.Argument_Count loop
            declare
               Value : constant String := Ada.Command_Line.Argument (Idx);
            begin
               if Is_Key (Value) then
                  --  Re-Adjust the index to parse this argument again.
                  Idx := Idx - 1;
                  exit;
               else
                  Values.Append (ASU.To_Unbounded_String (Value));
               end if;
               Idx := Idx + 1;
            end;
         end loop;
         Arguments.Insert (ASU.To_Unbounded_String (Key_Ind & Key), Values);
      end Parse_Values;

   --  ------------------------------------------------------------------------
   begin
      Arguments.Clear;
      while Idx <= Ada.Command_Line.Argument_Count loop
         declare
            Key : constant String := Ada.Command_Line.Argument (Idx);
         begin
            if Is_Key (Key) and then Key'Length > Key_Ind'Length then
               Parse_Values (Ada.Strings.Fixed.Tail (
                 Key, Key'Length - Key_Ind'Length));
            end if;
            Idx := Idx + 1;
         end;
      end loop;
   end Parse_Command_Line;

   --  ------------------------------------------------------------------------
   --  @Procedure: Set_Key_Indicator
   procedure Set_Key_Indicator (Key_Indicator : in String) is
   begin
      Key_Ind := Key_Indicator;
      Parse_Command_Line;
   end Set_Key_Indicator;

   --  ------------------------------------------------------------------------
   --  @Function: Has_Key
   function Has_Key (Key : in String) return Boolean is
   begin
      return Arguments.Contains (ASU.To_Unbounded_String (Key));
   end Has_Key;

   --  ------------------------------------------------------------------------
   --  @Function: Has_Values
   function Has_Values (Key : in String) return Boolean is
      use Ada.Containers;

      Values     : Values_Vector.Vector;
      Has_Values : Boolean := False;
   begin
      if Has_Key (Key) then
         Values     := Arguments.Element (ASU.To_Unbounded_String (Key));
         Has_Values := Values.Length >= 1;
      end if;
      return Has_Values;
   end Has_Values;

   --  ------------------------------------------------------------------------
   --  @Function: Get_Values
   function Get_Values (Key : in String) return Values_Type is
      Values : Values_Vector.Vector;
      Null_Values : Values_Type (1 .. 1);
   begin
      if Has_Values (Key) then
         Values := Arguments.Element (ASU.To_Unbounded_String (Key));
         declare
            Ret_Values   : Values_Type (1 .. Integer (Values.Length));
            Val_Idx      : Integer := Ret_Values'First;
            Element_Crsr : Values_Vector.Cursor := Values.First;
         begin
            while Values_Vector.Has_Element (Element_Crsr) loop
               Ret_Values (Val_Idx) := Values_Vector.Element (Element_Crsr);
               Element_Crsr := Values_Vector.Next (Element_Crsr);
               Val_Idx := Val_Idx + 1;
            end loop;
            return Ret_Values;
         end;
      end if;
      Null_Values (1) := ASU.Null_Unbounded_String;
      return Null_Values;
   end Get_Values;

--  ---------------------------------------------------------------------------
--  Initialization of the command line package.
begin
   Parse_Command_Line;
end Command_Line;
